﻿using System;
using System.Configuration;
using System.IO;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;

namespace ControleRepresentante.Common
{
    public static class EncryptExtensions
    {
        private static Guid m_Sid = new Guid("694148d2-fe4c-44d9-9d0d-53d322d1238c");
        
        public static string Encrypt(this string value)
        {
            using (Hash hash = new Hash(Hash.Provider.SHA1))
            {
                EncryptionData encData = new EncryptionData(value);
                hash.Calculate(encData, new EncryptionData(m_Sid.ToString()));
                return hash.Value.Base64;
            }
        }
    }

    /// <summary>
    /// Hash functions are fundamental to modern cryptography. These functions map binary 
    /// strings of an arbitrary length to small binary strings of a fixed length, known as 
    /// hash values. A cryptographic hash function has the property that it is computationally
    /// infeasible to find two distinct inputs that hash to the same value. Hash functions 
    /// are commonly used with digital signatures and for data integrity.
    /// </summary>
    public class Hash : IDisposable
    {

        /// <summary>
        /// Type of hash; some are security oriented, others are fast and simple
        /// </summary>
        public enum Provider
        {
            /// <summary>
            /// Cyclic Redundancy Check provider, 32-bit
            /// </summary>
            CRC32,
            /// <summary>
            /// Secure Hashing Algorithm provider, SHA-1 variant, 160-bit
            /// </summary>
            SHA1,
            /// <summary>
            /// Secure Hashing Algorithm provider, SHA-2 variant, 256-bit
            /// </summary>
            SHA256,
            /// <summary>
            /// Secure Hashing Algorithm provider, SHA-2 variant, 384-bit
            /// </summary>
            SHA384,
            /// <summary>
            /// Secure Hashing Algorithm provider, SHA-2 variant, 512-bit
            /// </summary>
            SHA512,
            /// <summary>
            /// Message Digest algorithm 5, 128-bit
            /// </summary>
            MD5
        }

        private HashAlgorithm m_Hash;
        private EncryptionData m_HashValue = new EncryptionData();

        private Hash()
        {
        }

        ~Hash()
        {
            Dispose(false);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
                ((IDisposable)m_Hash).Dispose();
        }

        /// <summary>
        /// Instantiate a new hash of the specified type
        /// </summary>
        public Hash(Provider p)
        {
            switch (p)
            {
                case Provider.CRC32:
                    m_Hash = new CRC32();
                    break;
                case Provider.MD5:
                    m_Hash = new MD5CryptoServiceProvider();
                    break;
                case Provider.SHA1:
                    m_Hash = new SHA1Managed();
                    break;
                case Provider.SHA256:
                    m_Hash = new SHA256Managed();
                    break;
                case Provider.SHA384:
                    m_Hash = new SHA384Managed();
                    break;
                case Provider.SHA512:
                    m_Hash = new SHA512Managed();
                    break;
            }
        }

        /// <summary>
        /// Returns the previously calculated hash
        /// </summary>
        public EncryptionData Value
        {
            get { return m_HashValue; }
        }

        /// <summary>
        /// Calculates hash on a stream of arbitrary length
        /// </summary>
        public EncryptionData Calculate(Stream s)
        {
            m_HashValue.Bytes = m_Hash.ComputeHash(s);
            return m_HashValue;
        }

        /// <summary>
        /// Calculates hash for fixed length <see cref="Data"/>
        /// </summary>
        public EncryptionData Calculate(EncryptionData d)
        {
            if (d == null)
                throw new ArgumentNullException("d");
            return CalculatePrivate(d.Bytes);
        }

        /// <summary>
        /// Calculates hash for a string with a prefixed salt value. 
        /// A "salt" is random data prefixed to every hashed value to prevent 
        /// common dictionary attacks.
        /// </summary>
        public EncryptionData Calculate(EncryptionData d, EncryptionData salt)
        {
            if (d == null)
                throw new ArgumentNullException("d");
            if (salt == null)
                throw new ArgumentNullException("salt");

            byte[] nb = new byte[d.Bytes.Length + salt.Bytes.Length];
            salt.Bytes.CopyTo(nb, 0);
            d.Bytes.CopyTo(nb, salt.Bytes.Length);
            return CalculatePrivate(nb);
        }

        /// <summary>
        /// Calculates hash for an array of bytes
        /// </summary>
        private EncryptionData CalculatePrivate(byte[] b)
        {
            m_HashValue.Bytes = m_Hash.ComputeHash(b);
            return m_HashValue;
        }

        private class CRC32 : HashAlgorithm
        {

            private int m_Result = -1;

            protected override void HashCore(byte[] array, int ibStart, int cbSize)
            {
                int lookup;
                for (int i = ibStart; i <= cbSize - 1; i++)
                {
                    lookup = (m_Result & 255) ^ array[i];
                    m_Result = ((m_Result & -256) / 256) & 16777215;
                    m_Result = m_Result ^ m_CrcLookup[lookup];
                }
            }

            protected override byte[] HashFinal()
            {
                byte[] b = BitConverter.GetBytes(~m_Result);
                Array.Reverse(b);
                return b;
            }

            public override void Initialize()
            {
                m_Result = -1;
            }

            private int[] m_CrcLookup = {0, 1996959894, -301047508, -1727442502, 124634137, 1886057615, -379345611, -1637575261, 249268274, 2044508324, 
			-522852066, -1747789432, 162941995, 2125561021, -407360249, -1866523247, 498536548, 1789927666, -205950648, -2067906082, 
			450548861, 1843258603, -187386543, -2083289657, 325883990, 1684777152, -43845254, -1973040660, 335633487, 1661365465, 
			-99664541, -1928851979, 997073096, 1281953886, -715111964, -1570279054, 1006888145, 1258607687, -770865667, -1526024853, 
			901097722, 1119000684, -608450090, -1396901568, 853044451, 1172266101, -589951537, -1412350631, 651767980, 1373503546, 
			-925412992, -1076862698, 565507253, 1454621731, -809855591, -1195530993, 671266974, 1594198024, -972236366, -1324619484, 
			795835527, 1483230225, -1050600021, -1234817731, 1994146192, 31158534, -1731059524, -271249366, 1907459465, 112637215, 
			-1614814043, -390540237, 2013776290, 251722036, -1777751922, -519137256, 2137656763, 141376813, -1855689577, -429695999, 
			1802195444, 476864866, -2056965928, -228458418, 1812370925, 453092731, -2113342271, -183516073, 1706088902, 314042704, 
			-1950435094, -54949764, 1658658271, 366619977, -1932296973, -69972891, 1303535960, 984961486, -1547960204, -725929758, 
			1256170817, 1037604311, -1529756563, -740887301, 1131014506, 879679996, -1385723834, -631195440, 1141124467, 855842277, 
			-1442165665, -586318647, 1342533948, 654459306, -1106571248, -921952122, 1466479909, 544179635, -1184443383, -832445281, 
			1591671054, 702138776, -1328506846, -942167884, 1504918807, 783551873, -1212326853, -1061524307, -306674912, -1698712650, 
			62317068, 1957810842, -355121351, -1647151185, 81470997, 1943803523, -480048366, -1805370492, 225274430, 2053790376, 
			-468791541, -1828061283, 167816743, 2097651377, -267414716, -2029476910, 503444072, 1762050814, -144550051, -2140837941, 
			426522225, 1852507879, -19653770, -1982649376, 282753626, 1742555852, -105259153, -1900089351, 397917763, 1622183637, 
			-690576408, -1580100738, 953729732, 1340076626, -776247311, -1497606297, 1068828381, 1219638859, -670225446, -1358292148, 
			906185462, 1090812512, -547295293, -1469587627, 829329135, 1181335161, -882789492, -1134132454, 628085408, 1382605366, 
			-871598187, -1156888829, 570562233, 1426400815, -977650754, -1296233688, 733239954, 1555261956, -1026031705, -1244606671, 
			752459403, 1541320221, -1687895376, -328994266, 1969922972, 40735498, -1677130071, -351390145, 1913087877, 83908371, 
			-1782625662, -491226604, 2075208622, 213261112, -1831694693, -438977011, 2094854071, 198958881, -2032938284, -237706686, 
			1759359992, 534414190, -2118248755, -155638181, 1873836001, 414664567, -2012718362, -15766928, 1711684554, 285281116, 
			-1889165569, -127750551, 1634467795, 376229701, -1609899400, -686959890, 1308918612, 956543938, -1486412191, -799009033, 
			1231636301, 1047427035, -1362007478, -640263460, 1088359270, 936918000, -1447252397, -558129467, 1202900863, 817233897, 
			-1111625188, -893730166, 1404277552, 615818150, -1160759803, -841546093, 1423857449, 601450431, -1285129682, -1000256840, 
			1567103746, 711928724, -1274298825, -1022587231, 1510334235, 755167117};

            public override byte[] Hash
            {
                get
                {
                    byte[] b = BitConverter.GetBytes(~m_Result);
                    Array.Reverse(b);
                    return b;
                }
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }

    /// <summary>
    /// Symmetric encryption uses a single key to encrypt and decrypt. 
    /// Both parties (encryptor and decryptor) must share the same secret key.
    /// </summary>
    public class Symmetric : IDisposable
    {

        private const string DefaultIntializationVector = "%1Az=-@qT";
        private const int BufferSize = 2048;

        public enum Provider
        {
            /// <summary>
            /// The Data Encryption Standard provider supports a 64 bit key only
            /// </summary>
            Des,
            /// <summary>
            /// The Rivest Cipher 2 provider supports keys ranging from 40 to 128 bits, default is 128 bits
            /// </summary>
            RC2,
            /// <summary>
            /// The Rijndael (also known as AES) provider supports keys of 128, 192, or 256 bits with a default of 256 bits
            /// </summary>
            Rijndael,
            /// <summary>
            /// The TripleDES provider (also known as 3DES) supports keys of 128 or 192 bits with a default of 192 bits
            /// </summary>
            TripleDes
        }

        private EncryptionData m_Key;
        private EncryptionData m_Iv;
        private SymmetricAlgorithm m_Crypto;

        private Symmetric()
        {
        }

        public Symmetric(Provider provider)
            : this(provider, true)
        {
        }

        ~Symmetric()
        {
            Dispose(false);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                ((IDisposable)m_Crypto).Dispose();
            }
        }

        /// <summary>
        /// Instantiates a new symmetric encryption object using the specified provider.
        /// </summary>
        public Symmetric(Provider provider, bool useDefaultInitializationVector)
        {
            switch (provider)
            {
                case Provider.Des:
                    m_Crypto = new DESCryptoServiceProvider();
                    break;
                case Provider.RC2:
                    m_Crypto = new RC2CryptoServiceProvider();
                    break;
                case Provider.Rijndael:
                    m_Crypto = new RijndaelManaged();
                    break;
                case Provider.TripleDes:
                    m_Crypto = new TripleDESCryptoServiceProvider();
                    break;
            }

            //-- make sure key and IV are always set, no matter what
            this.Key = RandomKey();
            if (useDefaultInitializationVector)
            {
                this.IntializationVector = new EncryptionData(DefaultIntializationVector);
            }
            else
            {
                this.IntializationVector = RandomInitializationVector();
            }
        }

        /// <summary>
        /// Key size in bytes. We use the default key size for any given provider; if you 
        /// want to force a specific key size, set this property
        /// </summary>
        public int KeySizeBytes
        {
            get { return m_Crypto.KeySize / 8; }
            set
            {
                checked
                {
                    m_Key.MaxBytes = value;
                    m_Crypto.KeySize = value * 8;
                }
            }
        }

        /// <summary>
        /// Key size in bits. We use the default key size for any given provider; if you 
        /// want to force a specific key size, set this property
        /// </summary>
        public int KeySizeBits
        {
            get { return m_Crypto.KeySize; }
            set
            {
                m_Crypto.KeySize = value;
                m_Key.MaxBits = value;
            }
        }

        /// <summary>
        /// The key used to encrypt/decrypt data
        /// </summary>
        public EncryptionData Key
        {
            get { return m_Key; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                m_Key = value;
                m_Key.MaxBytes = m_Crypto.LegalKeySizes[0].MaxSize / 8;
                m_Key.MinBytes = m_Crypto.LegalKeySizes[0].MinSize / 8;
                m_Key.StepBytes = m_Crypto.LegalKeySizes[0].SkipSize / 8;
            }
        }

        /// <summary>
        /// Using the default Cipher Block Chaining (CBC) mode, all data blocks are processed using
        /// the value derived from the previous block; the first data block has no previous data block
        /// to use, so it needs an InitializationVector to feed the first block
        /// </summary>
        public EncryptionData IntializationVector
        {
            get { return m_Iv; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                m_Iv = value;
                m_Iv.MaxBytes = m_Crypto.BlockSize / 8;
                m_Iv.MinBytes = m_Crypto.BlockSize / 8;
            }
        }

        /// <summary>
        /// generates a random Initialization Vector, if one was not provided
        /// </summary>
        public EncryptionData RandomInitializationVector()
        {
            m_Crypto.GenerateIV();
            EncryptionData d = new EncryptionData(m_Crypto.IV);
            return d;
        }

        /// <summary>
        /// generates a random Key, if one was not provided
        /// </summary>
        public EncryptionData RandomKey()
        {
            m_Crypto.GenerateKey();
            EncryptionData d = new EncryptionData(m_Crypto.Key);
            return d;
        }

        /// <summary>
        /// Ensures that _crypto object has valid Key and IV
        /// prior to any attempt to encrypt/decrypt anything
        /// </summary>
        private void ValidateKeyAndIv(bool isEncrypting)
        {
            if (m_Key.IsEmpty)
            {
                if (isEncrypting)
                {
                    m_Key = RandomKey();
                }
                else
                {
                    throw new CryptographicException("No key was provided for the decryption operation!");
                }
            }
            if (m_Iv.IsEmpty)
            {
                if (isEncrypting)
                {
                    m_Iv = RandomInitializationVector();
                }
                else
                {
                    throw new CryptographicException("No initialization vector was provided for the decryption operation!");
                }
            }
            m_Crypto.Key = m_Key.Bytes;
            m_Crypto.IV = m_Iv.Bytes;
        }

        /// <summary>
        /// Encrypts the specified Data using provided key
        /// </summary>
        public EncryptionData Encrypt(EncryptionData d, EncryptionData key)
        {
            this.Key = key;
            return Encrypt(d);
        }

        /// <summary>
        /// Encrypts the specified Data using preset key and preset initialization vector
        /// </summary>
        public EncryptionData Encrypt(EncryptionData d)
        {
            if (d == null)
                throw new ArgumentNullException("d");

            MemoryStream ms = new MemoryStream();

            ValidateKeyAndIv(true);

            CryptoStream cs = new CryptoStream(ms, m_Crypto.CreateEncryptor(), CryptoStreamMode.Write);
            cs.Write(d.Bytes, 0, d.Bytes.Length);
            cs.Close();
            ms.Close();

            return new EncryptionData(ms.ToArray());
        }

        /// <summary>
        /// Encrypts the stream to memory using provided key and provided initialization vector
        /// </summary>
        public EncryptionData Encrypt(Stream s, EncryptionData key, EncryptionData iv)
        {
            this.IntializationVector = iv;
            this.Key = key;
            return Encrypt(s);
        }

        /// <summary>
        /// Encrypts the stream to memory using specified key
        /// </summary>
        public EncryptionData Encrypt(Stream s, EncryptionData key)
        {
            this.Key = key;
            return Encrypt(s);
        }

        /// <summary>
        /// Encrypts the specified stream to memory using preset key and preset initialization vector
        /// </summary>
        public EncryptionData Encrypt(Stream s)
        {
            if (s == null)
                throw new ArgumentNullException("s");

            MemoryStream ms = new MemoryStream();
            byte[] b = new byte[BufferSize + 1];
            int i;

            ValidateKeyAndIv(true);

            CryptoStream cs = new CryptoStream(ms, m_Crypto.CreateEncryptor(), CryptoStreamMode.Write);
            i = s.Read(b, 0, BufferSize);
            while (i > 0)
            {
                cs.Write(b, 0, i);
                i = s.Read(b, 0, BufferSize);
            }

            cs.Close();
            ms.Close();

            return new EncryptionData(ms.ToArray());
        }

        /// <summary>
        /// Decrypts the specified data using provided key and preset initialization vector
        /// </summary>
        public EncryptionData Decrypt(EncryptionData encryptedData, EncryptionData key)
        {
            this.Key = key;
            return Decrypt(encryptedData);
        }

        /// <summary>
        /// Decrypts the specified stream using provided key and preset initialization vector
        /// </summary>
        public EncryptionData Decrypt(Stream encryptedStream, EncryptionData key)
        {
            this.Key = key;
            return Decrypt(encryptedStream);
        }

        /// <summary>
        /// Decrypts the specified stream using preset key and preset initialization vector
        /// </summary>
        public EncryptionData Decrypt(Stream encryptedStream)
        {
            System.IO.MemoryStream ms = new System.IO.MemoryStream();
            byte[] b = new byte[BufferSize + 1];

            ValidateKeyAndIv(false);
            CryptoStream cs = new CryptoStream(encryptedStream, m_Crypto.CreateDecryptor(), CryptoStreamMode.Read);

            int i;
            i = cs.Read(b, 0, BufferSize);

            while (i > 0)
            {
                ms.Write(b, 0, i);
                i = cs.Read(b, 0, BufferSize);
            }
            cs.Close();
            ms.Close();

            return new EncryptionData(ms.ToArray());
        }

        /// <summary>
        /// Decrypts the specified data using preset key and preset initialization vector
        /// </summary>
        public EncryptionData Decrypt(EncryptionData encryptedData)
        {
            if (encryptedData == null)
                throw new ArgumentNullException("encryptedData");

            MemoryStream ms = new MemoryStream(encryptedData.Bytes, 0, encryptedData.Bytes.Length);
            byte[] b = new byte[encryptedData.Bytes.Length];

            ValidateKeyAndIv(false);
            CryptoStream cs = new CryptoStream(ms, m_Crypto.CreateDecryptor(), CryptoStreamMode.Read);

            try
            {
                cs.Read(b, 0, encryptedData.Bytes.Length - 1);
            }
            catch (CryptographicException ex)
            {
                throw new CryptographicException("Unable to decrypt data. The provided key may be invalid.", ex);
            }
            finally
            {
                cs.Close();
            }
            return new EncryptionData(b);
        }


        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }

    /// <summary>
    /// Asymmetric encryption uses a pair of keys to encrypt and decrypt.
    /// There is a "public" key which is used to encrypt. Decrypting, on the other hand, 
    /// requires both the "public" key and an additional "private" key. The advantage is 
    /// that people can send you encrypted messages without being able to decrypt them.
    /// </summary>
    /// <remarks>
    /// The only provider supported is the <see cref="RSACryptoServiceProvider"/>
    /// </remarks>
    public class Asymmetric
    {

        private RSACryptoServiceProvider m_Rsa;
        private string m_KeyContainerName = "Encryption.AsymmetricEncryption.DefaultContainerName";
        private int m_KeySize = 1024;

        private const string ElementParent = "RSAKeyValue";
        private const string ElementModulus = "Modulus";
        private const string ElementExponent = "Exponent";
        private const string ElementPrimeP = "P";
        private const string ElementPrimeQ = "Q";
        private const string ElementPrimeExponentP = "DP";
        private const string ElementPrimeExponentQ = "DQ";
        private const string ElementCoefficient = "InverseQ";
        private const string ElementPrivateExponent = "D";

        //-- http://forum.java.sun.com/thread.jsp?forum=9&thread=552022&tstart=0&trange=15 
        private const string KeyModulus = "PublicKey.Modulus";
        private const string KeyExponent = "PublicKey.Exponent";
        private const string KeyPrimeP = "PrivateKey.P";
        private const string KeyPrimeQ = "PrivateKey.Q";
        private const string KeyPrimeExponentP = "PrivateKey.DP";
        private const string KeyPrimeExponentQ = "PrivateKey.DQ";
        private const string KeyCoefficient = "PrivateKey.InverseQ";
        private const string KeyPrivateExponent = "PrivateKey.D";

        /// <summary>
        /// Represents a public encryption key. Intended to be shared, it 
        /// contains only the Modulus and Exponent.
        /// </summary>
        /// 
        public class PublicKey
        {
            public string Modulus;
            public string Exponent;

            public PublicKey()
            {
            }

            public PublicKey(string keyXml)
            {
                LoadFromXml(keyXml);
            }

            /// <summary>
            /// Load public key from App.config or Web.config file
            /// </summary>
            public void LoadFromConfig()
            {
                this.Modulus = Utils.GetConfigString(KeyModulus);
                this.Exponent = Utils.GetConfigString(KeyExponent);
            }

            /// <summary>
            /// Returns *.config file XML section representing this public key
            /// </summary>
            public string ToConfigSection()
            {
                StringBuilder sb = new StringBuilder();
                {
                    sb.Append(Utils.WriteConfigKey(KeyModulus, this.Modulus));
                    sb.Append(Utils.WriteConfigKey(KeyExponent, this.Exponent));
                }
                return sb.ToString();
            }

            /// <summary>
            /// Writes the *.config file representation of this public key to a file
            /// </summary>
            public void ExportToConfigFile(string filePath)
            {
                StreamWriter sw = new StreamWriter(filePath, false);
                sw.Write(this.ToConfigSection());
                sw.Close();
            }

            /// <summary>
            /// Loads the public key from its XML string
            /// </summary>
            public void LoadFromXml(string keyXml)
            {
                this.Modulus = Utils.GetXmlElement(keyXml, "Modulus");
                this.Exponent = Utils.GetXmlElement(keyXml, "Exponent");
            }

            /// <summary>
            /// Converts this public key to an RSAParameters object
            /// </summary>
            public RSAParameters ToParameters()
            {
                RSAParameters r = new RSAParameters();
                r.Modulus = Convert.FromBase64String(this.Modulus);
                r.Exponent = Convert.FromBase64String(this.Exponent);
                return r;
            }

            /// <summary>
            /// Converts this public key to its XML string representation
            /// </summary>
            public string ToXml()
            {
                StringBuilder sb = new StringBuilder();
                {
                    sb.Append(Utils.WriteXmlNode(ElementParent));
                    sb.Append(Utils.WriteXmlElement(ElementModulus, this.Modulus));
                    sb.Append(Utils.WriteXmlElement(ElementExponent, this.Exponent));
                    sb.Append(Utils.WriteXmlNode(ElementParent, true));
                }
                return sb.ToString();
            }

            /// <summary>
            /// Writes the Xml representation of this public key to a file
            /// </summary>
            public void ExportToXmlFile(string filePath)
            {
                StreamWriter sw = new StreamWriter(filePath, false);
                sw.Write(this.ToXml());
                sw.Close();
            }

        }

        /// <summary>
        /// Represents a private encryption key. Not intended to be shared, as it 
        /// contains all the elements that make up the key.
        /// </summary>
        /// 
        public class PrivateKey
        {
            public string Modulus;
            public string Exponent;
            public string PrimeP;
            public string PrimeQ;
            public string PrimeExponentP;
            public string PrimeExponentQ;
            public string Coefficient;
            public string PrivateExponent;

            public PrivateKey()
            {
            }

            public PrivateKey(string keyXml)
            {
                LoadFromXml(keyXml);
            }

            /// <summary>
            /// Load private key from App.config or Web.config file
            /// </summary>
            public void LoadFromConfig()
            {
                this.Modulus = Utils.GetConfigString(KeyModulus);
                this.Exponent = Utils.GetConfigString(KeyExponent);
                this.PrimeP = Utils.GetConfigString(KeyPrimeP);
                this.PrimeQ = Utils.GetConfigString(KeyPrimeQ);
                this.PrimeExponentP = Utils.GetConfigString(KeyPrimeExponentP);
                this.PrimeExponentQ = Utils.GetConfigString(KeyPrimeExponentQ);
                this.Coefficient = Utils.GetConfigString(KeyCoefficient);
                this.PrivateExponent = Utils.GetConfigString(KeyPrivateExponent);
            }

            /// <summary>
            /// Converts this private key to an RSAParameters object
            /// </summary>
            public RSAParameters ToParameters()
            {
                RSAParameters r = new RSAParameters();
                r.Modulus = Convert.FromBase64String(this.Modulus);
                r.Exponent = Convert.FromBase64String(this.Exponent);
                r.P = Convert.FromBase64String(this.PrimeP);
                r.Q = Convert.FromBase64String(this.PrimeQ);
                r.DP = Convert.FromBase64String(this.PrimeExponentP);
                r.DQ = Convert.FromBase64String(this.PrimeExponentQ);
                r.InverseQ = Convert.FromBase64String(this.Coefficient);
                r.D = Convert.FromBase64String(this.PrivateExponent);
                return r;
            }

            /// <summary>
            /// Returns *.config file XML section representing this private key
            /// </summary>
            public string ToConfigSection()
            {
                StringBuilder sb = new StringBuilder();
                {
                    sb.Append(Utils.WriteConfigKey(KeyModulus, this.Modulus));
                    sb.Append(Utils.WriteConfigKey(KeyExponent, this.Exponent));
                    sb.Append(Utils.WriteConfigKey(KeyPrimeP, this.PrimeP));
                    sb.Append(Utils.WriteConfigKey(KeyPrimeQ, this.PrimeQ));
                    sb.Append(Utils.WriteConfigKey(KeyPrimeExponentP, this.PrimeExponentP));
                    sb.Append(Utils.WriteConfigKey(KeyPrimeExponentQ, this.PrimeExponentQ));
                    sb.Append(Utils.WriteConfigKey(KeyCoefficient, this.Coefficient));
                    sb.Append(Utils.WriteConfigKey(KeyPrivateExponent, this.PrivateExponent));
                }
                return sb.ToString();
            }

            /// <summary>
            /// Writes the *.config file representation of this private key to a file
            /// </summary>
            public void ExportToConfigFile(string strFilePath)
            {
                StreamWriter sw = new StreamWriter(strFilePath, false);
                sw.Write(this.ToConfigSection());
                sw.Close();
            }

            /// <summary>
            /// Loads the private key from its XML string
            /// </summary>
            public void LoadFromXml(string keyXml)
            {
                this.Modulus = Utils.GetXmlElement(keyXml, "Modulus");
                this.Exponent = Utils.GetXmlElement(keyXml, "Exponent");
                this.PrimeP = Utils.GetXmlElement(keyXml, "P");
                this.PrimeQ = Utils.GetXmlElement(keyXml, "Q");
                this.PrimeExponentP = Utils.GetXmlElement(keyXml, "DP");
                this.PrimeExponentQ = Utils.GetXmlElement(keyXml, "DQ");
                this.Coefficient = Utils.GetXmlElement(keyXml, "InverseQ");
                this.PrivateExponent = Utils.GetXmlElement(keyXml, "D");
            }

            /// <summary>
            /// Converts this private key to its XML string representation
            /// </summary>
            public string ToXml()
            {
                StringBuilder sb = new StringBuilder();
                {
                    sb.Append(Utils.WriteXmlNode(ElementParent));
                    sb.Append(Utils.WriteXmlElement(ElementModulus, this.Modulus));
                    sb.Append(Utils.WriteXmlElement(ElementExponent, this.Exponent));
                    sb.Append(Utils.WriteXmlElement(ElementPrimeP, this.PrimeP));
                    sb.Append(Utils.WriteXmlElement(ElementPrimeQ, this.PrimeQ));
                    sb.Append(Utils.WriteXmlElement(ElementPrimeExponentP, this.PrimeExponentP));
                    sb.Append(Utils.WriteXmlElement(ElementPrimeExponentQ, this.PrimeExponentQ));
                    sb.Append(Utils.WriteXmlElement(ElementCoefficient, this.Coefficient));
                    sb.Append(Utils.WriteXmlElement(ElementPrivateExponent, this.PrivateExponent));
                    sb.Append(Utils.WriteXmlNode(ElementParent, true));
                }
                return sb.ToString();
            }

            /// <summary>
            /// Writes the Xml representation of this private key to a file
            /// </summary>
            public void ExportToXmlFile(string filePath)
            {
                StreamWriter sw = new StreamWriter(filePath, false);
                sw.Write(this.ToXml());
                sw.Close();
            }

        }

        /// <summary>
        /// Instantiates a new asymmetric encryption session using the default key size; 
        /// this is usally 1024 bits
        /// </summary>
        public Asymmetric()
        {
            m_Rsa = GetRSAProvider();
        }

        /// <summary>
        /// Instantiates a new asymmetric encryption session using a specific key size
        /// </summary>
        public Asymmetric(int keySize)
        {
            m_KeySize = keySize;
            m_Rsa = GetRSAProvider();
        }

        /// <summary>
        /// Sets the name of the key container used to store this key on disk; this is an 
        /// unavoidable side effect of the underlying Microsoft CryptoAPI. 
        /// </summary>
        /// <remarks>
        /// http://support.microsoft.com/default.aspx?scid=http://support.microsoft.com:80/support/kb/articles/q322/3/71.asp&amp;NoWebContent=1
        /// </remarks>
        public string KeyContainerName
        {
            get { return m_KeyContainerName; }
            set { m_KeyContainerName = value; }
        }

        /// <summary>
        /// Returns the current key size, in bits
        /// </summary>
        public int KeySizeBits
        {
            get { return m_Rsa.KeySize; }
        }

        /// <summary>
        /// Returns the maximum supported key size, in bits
        /// </summary>
        public int KeySizeMaxBits
        {
            get { return m_Rsa.LegalKeySizes[0].MaxSize; }
        }

        /// <summary>
        /// Returns the minimum supported key size, in bits
        /// </summary>
        public int KeySizeMinBits
        {
            get { return m_Rsa.LegalKeySizes[0].MinSize; }
        }

        /// <summary>
        /// Returns valid key step sizes, in bits
        /// </summary>
        public int KeySizeStepBits
        {
            get { return m_Rsa.LegalKeySizes[0].SkipSize; }
        }

        /// <summary>
        /// Returns the default public key as stored in the *.config file
        /// </summary>
        public PublicKey DefaultPublicKey
        {
            get
            {
                PublicKey pubkey = new PublicKey();
                pubkey.LoadFromConfig();
                return pubkey;
            }
        }

        /// <summary>
        /// Returns the default private key as stored in the *.config file
        /// </summary>
        public PrivateKey DefaultPrivateKey
        {
            get
            {
                PrivateKey privkey = new PrivateKey();
                privkey.LoadFromConfig();
                return privkey;
            }
        }

        /// <summary>
        /// Generates a new public/private key pair as objects
        /// </summary>

        public static void GenerateNewKeyset(out PublicKey publicKey, out PrivateKey privateKey)
        {
            string publicKeyXML = null;
            string privateKeyXML = null;
            GenerateNewKeyset(out publicKeyXML, out privateKeyXML);
            publicKey = new PublicKey(publicKeyXML);
            privateKey = new PrivateKey(privateKeyXML);
        }

        /// <summary>
        /// Generates a new public/private key pair as XML strings
        /// </summary>
        /// 
        public static void GenerateNewKeyset(out string publicKeyXml, out string privateKeyXml)
        {
            RSA rsa = RSACryptoServiceProvider.Create();
            publicKeyXml = rsa.ToXmlString(false);
            privateKeyXml = rsa.ToXmlString(true);
        }

        /// <summary>
        /// Encrypts data using the default public key
        /// </summary>
        public EncryptionData Encrypt(EncryptionData d)
        {
            PublicKey PublicKey = DefaultPublicKey;
            return Encrypt(d, PublicKey);
        }

        /// <summary>
        /// Encrypts data using the provided public key
        /// </summary>
        public EncryptionData Encrypt(EncryptionData d, PublicKey publicKey)
        {
            if (publicKey == null)
                throw new ArgumentNullException("publicKey");

            m_Rsa.ImportParameters(publicKey.ToParameters());
            return EncryptPrivate(d);
        }

        /// <summary>
        /// Encrypts data using the provided public key as XML
        /// </summary>
        public EncryptionData Encrypt(EncryptionData d, string publicKeyXml)
        {
            LoadKeyXml(publicKeyXml, false);
            return EncryptPrivate(d);
        }

        private EncryptionData EncryptPrivate(EncryptionData d)
        {
            try
            {
                return new EncryptionData(m_Rsa.Encrypt(d.Bytes, false));
            }
            catch (CryptographicException ex)
            {
                if (ex.Message.ToLower().IndexOf("bad length") > -1)
                {
                    throw new CryptographicException("Your data is too large; RSA encryption is designed to encrypt relatively small amounts of data. The exact byte limit depends on the key size. To encrypt more data, use symmetric encryption and then encrypt that symmetric key with asymmetric RSA encryption.", ex);
                }
                else
                {
                    throw;
                }
            }
        }

        /// <summary>
        /// Decrypts data using the default private key
        /// </summary>
        public EncryptionData Decrypt(EncryptionData encryptedData)
        {
            PrivateKey PrivateKey = new PrivateKey();
            PrivateKey.LoadFromConfig();
            return Decrypt(encryptedData, PrivateKey);
        }

        /// <summary>
        /// Decrypts data using the provided private key
        /// </summary>
        public EncryptionData Decrypt(EncryptionData encryptedData, PrivateKey privateKey)
        {
            if (privateKey == null)
                throw new ArgumentNullException("privateKey");

            m_Rsa.ImportParameters(privateKey.ToParameters());
            return DecryptPrivate(encryptedData);
        }

        /// <summary>
        /// Decrypts data using the provided private key as XML
        /// </summary>
        public EncryptionData Decrypt(EncryptionData encryptedData, string privateKeyXml)
        {
            LoadKeyXml(privateKeyXml, true);
            return DecryptPrivate(encryptedData);
        }

        private void LoadKeyXml(string keyXml, bool isPrivate)
        {
            try
            {
                m_Rsa.FromXmlString(keyXml);
            }
            catch (XmlSyntaxException ex)
            {
                string s;
                if (isPrivate)
                {
                    s = "private";
                }
                else
                {
                    s = "public";
                }
                throw new XmlSyntaxException(string.Format("The provided {0} encryption key XML does not appear to be valid.", s), ex);
            }
        }

        private EncryptionData DecryptPrivate(EncryptionData encryptedData)
        {
            return new EncryptionData(m_Rsa.Decrypt(encryptedData.Bytes, false));
        }

        /// <summary>
        /// gets the default RSA provider using the specified key size; 
        /// note that Microsoft's CryptoAPI has an underlying file system dependency that is unavoidable
        /// </summary>
        /// <remarks>
        /// http://support.microsoft.com/default.aspx?scid=http://support.microsoft.com:80/support/kb/articles/q322/3/71.asp&amp;NoWebContent=1
        /// </remarks>
        private RSACryptoServiceProvider GetRSAProvider()
        {
            CspParameters csp = new CspParameters();
            csp.KeyContainerName = m_KeyContainerName;
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(m_KeySize, csp))
            {
                try
                {
                    rsa.PersistKeyInCsp = false;
                    RSACryptoServiceProvider.UseMachineKeyStore = true;
                    return rsa;
                }
                catch (System.Security.Cryptography.CryptographicException ex)
                {
                    if (ex.Message.ToLower().
                        IndexOf("csp for this implementation could not be acquired") > -1)
                    {
                        throw new SecurityException("Unable to obtain Cryptographic Service Provider. " + "Either the permissions are incorrect on the " + "'C:\\Documents and Settings\\All Users\\Application Data\\Microsoft\\Crypto\\RSA\\MachineKeys' " + "folder, or the current security context '" + System.Security.Principal.WindowsIdentity.GetCurrent().Name + "'" + " does not have access to this folder.", ex);
                    }
                    else
                    {
                        throw;
                    }
                }
            }
        }

    }

    /// <summary>
    /// represents Hex, Byte, Base64, or String data to encrypt/decrypt;
    /// use the .Text property to set/get a string representation 
    /// use the .Hex property to set/get a string-based Hexadecimal representation 
    /// use the .Base64 to set/get a string-based Base64 representation 
    /// </summary>
    public class EncryptionData
    {
        private byte[] m_B;
        private int m_MaxBytes;
        private int m_MinBytes;
        private int m_StepBytes;

        /// <summary>
        /// Determines the default text encoding across ALL Data instances
        /// </summary>
        private static Encoding m_DefaultEncoding = Encoding.GetEncoding("Windows-1252");

        public static Encoding DefaultEncoding
        {
            get
            {
                return m_DefaultEncoding;
            }
        }

        /// <summary>
        /// Determines the default text encoding for this Data instance
        /// </summary>
        /// 
        public Encoding Encoding = DefaultEncoding;

        /// <summary>
        /// Creates new, empty encryption data
        /// </summary>
        public EncryptionData()
        {
        }

        /// <summary>
        /// Creates new encryption data with the specified byte array
        /// </summary>
        public EncryptionData(byte[] b)
        {
            m_B = b;
        }

        /// <summary>
        /// Creates new encryption data with the specified string; 
        /// will be converted to byte array using default encoding
        /// </summary>
        public EncryptionData(string s)
        {
            this.Text = s;
        }

        /// <summary>
        /// Creates new encryption data using the specified string and the 
        /// specified encoding to convert the string to a byte array.
        /// </summary>
        public EncryptionData(string s, System.Text.Encoding encoding)
        {
            this.Encoding = encoding;
            this.Text = s;
        }

        /// <summary>
        /// returns true if no data is present
        /// </summary>
        public bool IsEmpty
        {
            get
            {
                if (m_B == null)
                {
                    return true;
                }
                if (m_B.Length == 0)
                {
                    return true;
                }
                return false;
            }
        }

        /// <summary>
        /// allowed step interval, in bytes, for this data; if 0, no limit
        /// </summary>
        public int StepBytes
        {
            get { return m_StepBytes; }
            set { m_StepBytes = value; }
        }

        /// <summary>
        /// allowed step interval, in bits, for this data; if 0, no limit
        /// </summary>
        public int StepBits
        {
            get { return m_StepBytes * 8; }
            set { m_StepBytes = value / 8; }
        }

        /// <summary>
        /// minimum number of bytes allowed for this data; if 0, no limit
        /// </summary>
        public int MinBytes
        {
            get { return m_MinBytes; }
            set { m_MinBytes = value; }
        }

        /// <summary>
        /// minimum number of bits allowed for this data; if 0, no limit
        /// </summary>
        public int MinBits
        {
            get { return m_MinBytes * 8; }
            set { m_MinBytes = value / 8; }
        }

        /// <summary>
        /// maximum number of bytes allowed for this data; if 0, no limit
        /// </summary>
        public int MaxBytes
        {
            get { return m_MaxBytes; }
            set { m_MaxBytes = value; }
        }

        /// <summary>
        /// maximum number of bits allowed for this data; if 0, no limit
        /// </summary>
        public int MaxBits
        {
            get { return m_MaxBytes * 8; }
            set { m_MaxBytes = value / 8; }
        }

        /// <summary>
        /// Returns the byte representation of the data; 
        /// This will be padded to MinBytes and trimmed to MaxBytes as necessary!
        /// </summary>
        /// 
        public byte[] Bytes
        {
            get
            {
                if (m_MaxBytes > 0)
                {
                    if (m_B.Length > m_MaxBytes)
                    {
                        byte[] b = new byte[m_MaxBytes];
                        Array.Copy(m_B, b, b.Length);
                        m_B = b;
                    }
                }
                if (m_MinBytes > 0)
                {
                    if (m_B.Length < m_MinBytes)
                    {
                        byte[] b = new byte[m_MinBytes];
                        Array.Copy(m_B, b, m_B.Length);
                        m_B = b;
                    }
                }
                return m_B;
            }
            set { m_B = value; }
        }

        /// <summary>
        /// Sets or returns text representation of bytes using the default text encoding
        /// </summary>
        public string Text
        {
            get
            {
                if (m_B == null)
                {
                    return "";
                }
                else
                {
                    //-- need to handle nulls here; oddly, C# will happily convert
                    //-- nulls into the string whereas VB stops converting at the
                    //-- first null!
                    int i = Array.IndexOf(m_B, (byte)0);
                    if (i >= 0)
                    {
                        return this.Encoding.GetString(m_B, 0, i);
                    }
                    else
                    {
                        return this.Encoding.GetString(m_B);
                    }
                }
            }
            set { m_B = this.Encoding.GetBytes(value); }
        }

        /// <summary>
        /// Sets or returns Hex string representation of this data
        /// </summary>
        public string Hex
        {
            get { return Utils.ToHex(m_B); }
            set { m_B = Utils.FromHex(value); }
        }

        /// <summary>
        /// Sets or returns Base64 string representation of this data
        /// </summary>
        public string Base64
        {
            get { return Utils.ToBase64(m_B); }
            set { m_B = Utils.FromBase64(value); }
        }

        /// <summary>
        /// Returns text representation of bytes using the default text encoding
        /// </summary>
        public new string ToString()
        {
            return this.Text;
        }

        /// <summary>
        /// returns Base64 string representation of this data
        /// </summary>
        public string ToBase64()
        {
            return this.Base64;
        }

        /// <summary>
        /// returns Hex string representation of this data
        /// </summary>
        public string ToHex()
        {
            return this.Hex;
        }

    }

    /// <summary>
    /// Friend class for shared utility methods used by multiple Encryption classes
    /// </summary>
    internal static class Utils
    {

        /// <summary>
        /// converts an array of bytes to a string Hex representation
        /// </summary>
        static internal string ToHex(byte[] ba)
        {
            if (ba == null || ba.Length == 0)
            {
                return "";
            }
            const string HexFormat = "{0:X2}";
            StringBuilder sb = new StringBuilder();
            foreach (byte b in ba)
            {
                sb.Append(string.Format(HexFormat, b));
            }
            return sb.ToString();
        }

        /// <summary>
        /// converts from a string Hex representation to an array of bytes
        /// </summary>
        static internal byte[] FromHex(string hexEncoded)
        {
            if (hexEncoded == null || hexEncoded.Length == 0)
            {
                return null;
            }
            try
            {
                int l = Convert.ToInt32(hexEncoded.Length / 2);
                byte[] b = new byte[l];
                for (int i = 0; i <= l - 1; i++)
                {
                    b[i] = Convert.ToByte(hexEncoded.Substring(i * 2, 2), 16);
                }
                return b;
            }
            catch (Exception ex)
            {
                throw new System.FormatException("The provided string does not appear to be Hex encoded:" + Environment.NewLine + hexEncoded + Environment.NewLine, ex);
            }
        }

        /// <summary>
        /// converts from a string Base64 representation to an array of bytes
        /// </summary>
        static internal byte[] FromBase64(string base64Encoded)
        {
            if (base64Encoded == null || base64Encoded.Length == 0)
            {
                return null;
            }
            try
            {
                return Convert.FromBase64String(base64Encoded);
            }
            catch (System.FormatException ex)
            {
                throw new System.FormatException("The provided string does not appear to be Base64 encoded:" + Environment.NewLine + base64Encoded + Environment.NewLine, ex);
            }
        }

        /// <summary>
        /// converts from an array of bytes to a string Base64 representation
        /// </summary>
        static internal string ToBase64(byte[] b)
        {
            if (b == null || b.Length == 0)
            {
                return "";
            }
            return Convert.ToBase64String(b);
        }

        /// <summary>
        /// retrieve an element from an XML string
        /// </summary>
        static internal string GetXmlElement(string xml, string element)
        {
            Match m;
            m = Regex.Match(xml, "<" + element + ">(?<Element>[^>]*)</" + element + ">", RegexOptions.IgnoreCase);
            if (m == null)
            {
                throw new SecurityException("Could not find <" + element + "></" + element + "> in provided Public Key XML.");
            }
            return m.Groups["Element"].ToString();
        }

        static internal string GetConfigString(string key)
        {
            return GetConfigString(key, true);
        }

        /// <summary>
        /// Returns the specified string value from the application .config file
        /// </summary>
        static internal string GetConfigString(string key, bool isRequired)
        {

            string s = (string)ConfigurationManager.AppSettings.Get(key);
            if (s == null)
            {
                if (isRequired)
                {
                    throw new ConfigurationErrorsException("key <" + key + "> is missing from .config file");
                }
                else
                {
                    return "";
                }
            }
            else
            {
                return s;
            }
        }

        static internal string WriteConfigKey(string key, string value)
        {
            string s = "<add key=\"{0}\" value=\"{1}\" />" + Environment.NewLine;
            return string.Format(s, key, value);
        }

        static internal string WriteXmlElement(string element, string value)
        {
            string s = "<{0}>{1}</{0}>" + Environment.NewLine;
            return string.Format(s, element, value);
        }

        static internal string WriteXmlNode(string element)
        {
            return WriteXmlNode(element, false);
        }

        static internal string WriteXmlNode(string element, bool isClosing)
        {
            string s;
            if (isClosing)
            {
                s = "</{0}>" + Environment.NewLine;
            }
            else
            {
                s = "<{0}>" + Environment.NewLine;
            }
            return string.Format(s, element);
        }

    }
}